﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using Tycho.Lexer;
using Tycho.Modules;
using Tycho.Runtime;
using Tycho.Compiler;
using Tycho.Parser.Tokens;
using System.IO;
using System.Transactions;
using Tycho.Language;
using System.Reflection;
using Tycho.Native;

namespace CommandLine {
    class Program {
        static void Main (string [] args) {
            Evaluate (args);
//            PrintSourceLocations ("test-tokens.txt");
        }

        private static void PrintSourceLocations (string filename) {
            List<Token> tokens = NativeLexer.Lex (File.ReadAllText (filename), filename);

            PrintSourceLocations (tokens);
            Console.ReadKey ();
        }

        private static void PrintSourceLocations (List<Token> tokens) {
            foreach (Token token in tokens) {
                PrintSourceLocation (token.SourceLocation);

                if (token is BracketToken) {
                    PrintSourceLocations (((BracketToken) token).Tokens);
                }
            }
        }

        private static void Evaluate (string[] args) {
            TopLevel toplevel = new TopLevel (Namespaces.User);
            toplevel.AddModule (new AssemblyModuleLoader (Namespaces.Tycho.GetNamespace ("native"), Assembly.Load ("Tycho.Native")));
            Dictionary<Namespace, List<AssemblyName>> assemblyMap = BuildNativeAssemblyMap ();
            toplevel.AddModule (new NativeModuleLoader (assemblyMap));
            toplevel.AddModule (new HttpModuleLoader (ConfigurationManager.AppSettings["BaseModuleUrl"], toplevel.ModuleFrame));

            using (new ThreadContext ()) {
                ThreadContext.Current.SetProperty (Symbols.RuntimePrintStream, new NativeObject<TextWriter> (Console.Out));

                if (args.Length == 0) {
                    while (true) {
                        try {
                            Console.Write ("> ");
                            string line = Console.ReadLine ();
                            Console.WriteLine (toplevel.Evaluate (line).ToString ());
                        } catch (CompilationException cex) {
                            PrintCompilationException (cex);
                        } catch (ExitException) {
                            break;
                        } catch (TychoException tex) {
                            Console.WriteLine ("error: " + tex.Message);
                        }
                    }
                } else {
                    foreach (string fileName in args) {
                        try {
                            AnyObject result = toplevel.Evaluate (File.ReadAllText (fileName), fileName);
                            Console.WriteLine ("> " + result);
                        } catch (CompilationException cex) {
                            PrintCompilationException (cex);
                        } catch (ExitException) {
                            break;
                        } catch (TychoException tex) {
                            Console.WriteLine ("error: " + tex.Message);
                        }
                    }
                }
            }
        }

        private static Dictionary<Namespace, List<AssemblyName>> BuildNativeAssemblyMap () {
            var assemblyMap = new Dictionary<Namespace, List<AssemblyName>>();
            assemblyMap [Namespace.Parse ("system")] = new List<AssemblyName> {
                                                                                  new AssemblyName("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
                                                                                  new AssemblyName("mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
                                                                              };
            return assemblyMap;
        }

        static string PadLeftSpaces (int number, int padToLength) {
            string numberString = number.ToString ();

            return new String (' ', padToLength - numberString.Length) + numberString;
        }

        static void PrintCompilationException (CompilationException ex) {
            SourceLocation sloc = ex.SourceLocation;

            PrintSourceLocation (sloc);

            Console.WriteLine ("error: " + ex.Message);
        }

        private static void PrintSourceLocation (SourceLocation sloc) {
            if (sloc.FileName != null) {
                Console.WriteLine ("file: " + sloc.FileName);
            }

            if (sloc.LineStart != sloc.LineEnd) {
                StringReader source = new StringReader (sloc.Source);

                for (int n = 0; n < sloc.LineStart - 1; n++) {
                    source.ReadLine ();
                }

                int lineNumberLength = sloc.LineEnd.ToString ().Length;

                for (int n = sloc.LineStart; n <= sloc.LineEnd; n++) {
                    Console.WriteLine (PadLeftSpaces (n, lineNumberLength) + ": > " + source.ReadLine ());
                }
            } else {
                StringReader source = new StringReader (sloc.Source);

                for (int n = 0; n < sloc.LineStart - 1; n++) {
                    source.ReadLine ();
                }

                string lineNumber = sloc.LineEnd + ": ";
                string line = source.ReadLine ();
                string indicatorLine = new String (' ', lineNumber.Length) + GetPaddingFromIndent (line, sloc.ColumnStart) + new String ('^', sloc.ColumnEnd - sloc.ColumnStart);

                Console.WriteLine (lineNumber + line);
                Console.WriteLine (indicatorLine);
            }
        }

        static string GetPaddingFromIndent (string errorLine, int errorStart) {
            StringBuilder sb = new StringBuilder (errorStart);
            char c = errorLine [0];
            int n = 0;

            while (n < errorLine.Length && n < errorStart && char.IsWhiteSpace (c = errorLine [n])) {
                sb.Append (c);
                n++;
            }

            while (n < errorStart) {
                sb.Append (' ');
                n++;
            }

            return sb.ToString ();
        }
    }
}
